#region Using directives

using System;
using System.Reflection;
using System.Reflection.Emit;
using System.Collections.Generic;
using System.Text;

#endregion

namespace Ndo
{
    internal class DegenerateFactory<T> : BoxFactory<T>
    {
        Predicate<MemberInfo> degeneratePredicate;
        internal DegenerateFactory(string typename, Predicate<MemberInfo> degeneratePredicate)
        {
            this.degeneratePredicate = degeneratePredicate;
            Type[] ts = new Type[] { typeof(T) };
            //TypeBuild
            tb = BoxHelper.BoxModuleBuilder.DefineType(
                typename,
                TypeAttributes.NotPublic);

            //FieldBuilder
            fb = tb.DefineField(TargetFieldName, typeof(T), BoxHelper.PrivateFieldAttribute);

            //ConstructorBuild
            ConstructorBuilder cb = tb.DefineConstructor(
                MethodAttributes.Public,
                CallingConventions.Standard,
                ts);
            ILGenerator g = cb.GetILGenerator();
            g.Emit(OpCodes.Ldarg_0);
            g.Emit(OpCodes.Call, BaseConstructor);
            g.Emit(OpCodes.Ldarg_0);
            g.Emit(OpCodes.Ldarg_1);
            g.Emit(OpCodes.Stfld, fb);
            g.Emit(OpCodes.Ret);

            if (typeof(T).IsInterface)
            {
                tb.AddInterfaceImplementation(typeof(T));
                ImplementMembers(typeof(T), true, BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly);
            }
            else
            {
                tb.SetParent(typeof(T));
                ImplementMembers(typeof(T), false, BindingFlags.Public | BindingFlags.Instance);
            }
            foreach(Type t in typeof(T).GetInterfaces())
            {
                tb.AddInterfaceImplementation(t);
                ImplementMembers(t, true, BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly);
            }

            ////TODO: Permission
            ////Implement of IBoxed<T>
            //tb.AddInterfaceImplementation(typeof(IBoxed<T>));
            //MethodInfo Getter;
            //MethodBuilder mb;
            ////Implement of TargetGetter
            //Getter = typeof(IBoxed<T>).GetProperty("Target").GetGetMethod(false);
            //mb = tb.DefineMethod(Getter.Name, BoxHelper.InterfaceMethodAttribute, typeof(T), BoxHelper.EmptyTypes);
            //g = mb.GetILGenerator();
            //g.Emit(OpCodes.Ldarg_0);
            //g.Emit(OpCodes.Ldfld, fb);
            //g.Emit(OpCodes.Ret);
            //tb.DefineMethodOverride(mb, Getter);

            //Finished
            CI = tb.CreateType().GetConstructor(ts);
        }
        private void ImplementMembers(Type type, bool isInterface, BindingFlags bindingFlags)
        {
            bool dg;
            //MethodBuild
            foreach (MethodInfo mi in type.GetMethods(bindingFlags))
                if (!mi.IsSpecialName && mi.IsVirtual)
                {
                    ImplementMethod(mi, isInterface, false);
                }

            //PropertyBuild
            foreach (PropertyInfo pi in type.GetProperties(bindingFlags))
            {
                dg = degeneratePredicate(pi);
                MethodInfo mi = pi.GetGetMethod(false);
                if (mi != null)
                {
                    ImplementMethod(mi, isInterface, dg);
                }
                mi = pi.GetSetMethod(false);
                if (mi != null)
                {
                    ImplementMethod(mi, isInterface, dg);
                }
            }

            //EventBuild
            foreach (EventInfo ei in type.GetEvents(bindingFlags))
            {
                dg = degeneratePredicate(ei);
                MethodInfo mi = ei.GetAddMethod(false);
                if (mi != null)
                {
                    ImplementMethod(mi, isInterface, dg);
                }
                mi = ei.GetRemoveMethod(false);
                if (mi != null)
                {
                    ImplementMethod(mi, isInterface, dg);
                }
            }
        }
        private void ImplementMethod(MethodInfo mi, bool isInterface, bool dg)
        {
            MethodAttributes ma;
            if (isInterface)
                ma = BoxHelper.InterfaceMethodAttribute;
            else
                ma = mi.Attributes;
            System.Diagnostics.Trace.WriteLine(string.Format("{0} {1} {2}", mi, isInterface, dg));
            if (dg || degeneratePredicate(mi))
            {
                ImplementMethodDegenerate(ma, mi);
            }
            else
            {
                ImplementMethodInvoke(ma, mi);
            }
        }
        private void ImplementMethodDegenerate(MethodAttributes ma, MethodInfo mi)
        {
            MethodBuilder mb = tb.DefineMethod(mi.Name, ma, mi.ReturnType, BoxHelper.GetParamsTypes(mi.GetParameters()));
            ILGenerator g = mb.GetILGenerator();
            g.Emit(OpCodes.Call, BoxHelper.CurrentMethodGetter);
            g.Emit(OpCodes.Newobj, InvokeDegeneratedMethodException.Constructor);
            g.Emit(OpCodes.Throw);
            tb.DefineMethodOverride(mb, mi);
        }
    }
}
